import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Notify } from './entities/notify.entity';
import { In, Repository, MoreThan, LessThan, DataSource } from 'typeorm';
import { SubjectManager } from './subjects';
import { paginate } from 'nestjs-typeorm-paginate';
import { validateParams } from 'src/common/utils';
import { CreateNotifyDto } from './dto/create-notify.dto';
import { plainToInstance } from 'class-transformer';
import { UpdateNotifyDto } from './dto/update-notify.dto';
import { NOTIFY_STATUS, PUSH_STATUS } from './config';
import axios from 'axios';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

@Injectable()
export class NotifyService {
    constructor(
        @InjectRepository(Notify) private notifyRepository: Repository<Notify>,
        private connect: DataSource,
        @InjectQueue('notify') private notifyQueue: Queue,
    ) { }

    /**
     * 前端通知
     * 数据库插入数据
     *
     * input.reliable 是否可靠通知，默认可靠
     * input.delay 延迟时间，默认 0 秒
     * input.data 通知数据，需要入库的数据
     *
     * @param {input} input
     * @memberof NotifyService
     */
    /**
     * 推送消息入口
     * 支持可靠推送和即时推送两种模式
     * 
     * @param input.reliable 是否可靠推送，默认 true
     * @param input.delay 延迟时间（毫秒），默认 0
     * @param input.data 推送数据数组
     */
    async saveNotify(input: any = {}): Promise<any> {
        const { reliable = true, delay = 0, data } = input || {};

        // 参数校验
        if (!data || !Array.isArray(data) || data.length === 0) {
            throw new BadRequestException('推送数据不能为空');
        }
        
        if (data.length > 10000) {
            throw new BadRequestException('单次最多支持 1 万接收人');
        }

        try {
            if (reliable) {
                // 可靠推送：入库 + 队列处理，支持重试
                await this.processReliablePush(data, delay);
            } else {
                // 即时推送：直接推送，不入库
                await this.processInstantPush(data, delay);
            }
            
            return {
                success: true,
                message: `${reliable ? '可靠' : '即时'}推送任务已提交`,
                count: data.length
            };
        } catch (error) {
            Logger.error(`推送失败: ${error.message}`, error.stack);
            throw new BadRequestException(`推送失败: ${error.message}`);
        }
    }

    /**
     * 可靠推送处理
     * 特点：数据入库、支持重试、对数据库压力小
     */
    private async processReliablePush(data: any[], delay: number = 0): Promise<void> {
        const BATCH_SIZE = 500; // 批量处理大小，减少数据库压力
        
        // 分批处理大数据量
        for (let i = 0; i < data.length; i += BATCH_SIZE) {
            const batch = data.slice(i, i + BATCH_SIZE);
            await this.saveAndQueueReliableBatch(batch, delay + i * 10); // 错开延迟避免峰值
        }
    }

    /**
     * 保存可靠推送批次到数据库并加入队列
     */
    private async saveAndQueueReliableBatch(batch: any[], delay: number): Promise<void> {
        // 补充必要字段
        const completeData = batch.map((item) => ({
            ...item,
            status: NOTIFY_STATUS.UNREAD,
            push_status: PUSH_STATUS.PENDING,
            read_at: null,
        }));

        // 数据校验
        const validatedData = await this.validateBatchData(completeData);

        // 事务处理：确保数据一致性
        const queryRunner = this.connect.createQueryRunner();
        await queryRunner.connect();
        await queryRunner.startTransaction();

        try {
            // 批量保存到数据库
            const savedNotifies = await queryRunner.manager.save(Notify, validatedData, { 
                chunk: 100 // 分块保存，减少内存占用
            });
            
            await queryRunner.commitTransaction();

            // 加入可靠推送队列，支持重试
            await this.notifyQueue.add(
                'reliable-push-batch',
                { notifyIds: savedNotifies.map(n => n.id) },
                {
                    attempts: 3, // 最多重试2次（总共3次尝试）
                    backoff: {
                        type: 'exponential',
                        delay: 2000, // 指数退避，从2秒开始
                    },
                    delay,
                }
            );

        } catch (error) {
            await queryRunner.rollbackTransaction();
            throw error;
        } finally {
            await queryRunner.release();
        }
    }

    /**
     * 即时推送处理
     * 特点：不入库、直接推送、失败放弃
     */
    private async processInstantPush(data: any[], delay: number = 0): Promise<void> {
        const BATCH_SIZE = 100; // 即时推送批次较小，保证响应速度
        
        // 分批处理
        for (let i = 0; i < data.length; i += BATCH_SIZE) {
            const batch = data.slice(i, i + BATCH_SIZE);
            
            // 加入即时推送队列
            await this.notifyQueue.add(
                'instant-push-batch',
                { data: batch },
                {
                    attempts: 3, // 最多重试2次（总共3次尝试）
                    backoff: {
                        type: 'fixed',
                        delay: 1000, // 固定1秒重试间隔
                    },
                    delay: delay + i * 5, // 错开延迟
                    removeOnComplete: 5, // 即时推送完成后快速清理
                    removeOnFail: 20,
                }
            );
        }
    }
    /**
     * 批量校验通知数据（用于可靠通知）
     * @param data 待校验的通知数据数组
     * @returns 校验通过的数据实例
     */
    private async validateBatchData(data: any[]): Promise<CreateNotifyDto[]> {
        if (!Array.isArray(data) || data.length === 0)
            throw new BadRequestException('通知数据不能为空');

        const validatedData: CreateNotifyDto[] = [];
        for (let i = 0; i < data.length; i++) {
            const instance = plainToInstance(CreateNotifyDto, data[i]);
            try {
                await validateParams(instance);
                validatedData.push(instance);
            } catch (error) {
                // 遇到第一个错误立即返回
                throw new BadRequestException(
                    `第 ${i + 1} 条数据校验失败: ${error.message}`,
                );
            }
        }

        return validatedData;
    }

    // 给用户推送消息
    async toSend(data: any) {
        const { receiver_id, ...rest } = data || {};
        if (!receiver_id) return;

        const stream = SubjectManager.SseSubject.get(receiver_id.toString());
        if (stream && !stream.closed) {
            stream.next(data);
            SubjectManager.updateActivity(receiver_id.toString()); // 更新活动时间
        }
    }

    // 查询记录
    async findNotify(obj: UpdateNotifyDto | { id: number }): Promise<any> {
        return await this.notifyRepository.findOne({ where: obj });
    }

    // 根据ID数组查询待推送的通知
    async findNotifyByIds(notifyIds: number[]) {
        return await this.notifyRepository.find({
            where: {
                id: In(notifyIds),
                push_status: PUSH_STATUS.PENDING
            }
        });
    }
    // 更新记录
    async updateNotify(id: number, obj: UpdateNotifyDto) {
        return await this.notifyRepository.update(id, obj);
    }

    // 批量更新通知状态
    async batchUpdateNotifyStatus(notifyIds: number[], status: number) {
        return await this.notifyRepository.update(
            { 
                id: In(notifyIds),
                push_status: PUSH_STATUS.PENDING
            },
            { push_status: status }
        );
    }




    // 查询所有记录
    async findAll(paginateOptions: any, receiver_id: number) {
        return await paginate(this.notifyRepository, paginateOptions, {
            where: {
                receiver_id: receiver_id.toString(),
                push_status: PUSH_STATUS.SUCCESS, // 只查询已推送的消息
            },
            order: { created_at: 'DESC' },
        });
    }

    // 查询单条记录
    async findOne(id: number, receiver_id: number) {
        const notify = await this.notifyRepository.findOne({
            where: {
                id,
                receiver_id: receiver_id.toString(),
                push_status: PUSH_STATUS.SUCCESS, // 只允许查看已推送的消息
            },
        });

        if (!notify) {
            throw new BadRequestException('消息不存在');
        }

        // 如果是未读消息，标记为已读
        if (notify.status === NOTIFY_STATUS.UNREAD) {
            await this.notifyRepository.update(id, { status: NOTIFY_STATUS.READ });
            // 重新发送未读数量
            await this.getUnReadCount(receiver_id);
        }

        return notify;
    }

    // 获取上条消息
    async getPrevNotifyId(id: number, receiver_id: number) {
        const prevNotify = await this.notifyRepository.findOne({
            where: {
                receiver_id: receiver_id.toString(),
                id: MoreThan(id),
                push_status: PUSH_STATUS.SUCCESS, // 只在已推送的消息中查找
            },
            order: {
                id: 'ASC',
            },
        });

        return { prevId: prevNotify?.id || null };
    }

    // 获取下条消息
    async getNextNotifyId(id: number, receiver_id: number) {
        const nextNotify = await this.notifyRepository.findOne({
            where: {
                receiver_id: receiver_id.toString(),
                id: LessThan(id),
                push_status: PUSH_STATUS.SUCCESS, // 只在已推送的消息中查找
            },
            order: {
                id: 'DESC',
            },
        });

        return { nextId: nextNotify?.id || null };
    }

    // 删除单条
    async remove(id: number, receiver_id: number) {
        const exist = await this.notifyRepository.findOne({
            where: {
                id,
                receiver_id: receiver_id.toString(),
                push_status: PUSH_STATUS.SUCCESS, // 只允许删除已推送的消息
            },
        });
        if (!exist) throw new BadRequestException(`已删除`);
        const res = await this.notifyRepository.delete(id);

        // 重新发送消息
        await this.getUnReadCount(receiver_id);

        return res;
    }

    // 清空已读消息
    async clearRead(receiver_id: number) {
        const list = await this.notifyRepository.find({
            where: {
                receiver_id: receiver_id.toString(),
                status: NOTIFY_STATUS.UNREAD, // 未读状态
                push_status: PUSH_STATUS.SUCCESS, // 只操作已推送的消息
            },
        });
        if (!list.length) return '操作成功';
        const ids = list.map((i) => i.id);
        const res = await this.notifyRepository.update(
            { id: In(ids) },
            { status: NOTIFY_STATUS.READ },
        );

        // 重新发送消息
        await this.getUnReadCount(receiver_id);

        return '操作成功';
    }

    // 清空消息
    async clear(receiver_id: number) {
        const res = await this.notifyRepository.delete({
            receiver_id: receiver_id.toString(),
            push_status: PUSH_STATUS.SUCCESS, // 只删除已推送的消息
        });

        // 重新发送消息
        await this.getUnReadCount(receiver_id);

        return res;
    }

    // 批量、单条已读
    async read(receiver_id: number, ids: string[]) {
        const res = await this.notifyRepository.update(
            {
                id: In(ids),
                receiver_id: receiver_id.toString(),
                push_status: PUSH_STATUS.SUCCESS, // 只能标记已推送的消息为已读
            },
            { status: NOTIFY_STATUS.READ },
        );

        // 重新发送消息
        await this.getUnReadCount(receiver_id);

        return res;
    }

    // 未读数量
    async getUnReadCount(receiver_id: number) {
        try {
            const list = await this.notifyRepository.find({
                where: {
                    receiver_id: receiver_id.toString(),
                    status: NOTIFY_STATUS.UNREAD, // 未读状态
                    push_status: PUSH_STATUS.SUCCESS, // 已推送状态
                },
            });
            this.toSend({ unReadCount: list.length, receiver_id: receiver_id });
        } catch (e) {
            this.wxSend(`信息推送失败：${e.message || ''}`);
        }
    }

    // 企微发送信息，content会自动截取，注意别超2048字符
    async wxSend(content: string) {
        const contentSize = 2048;
        const hookUrl = '';
        const postData = {
            msgtype: 'text',
            text: { content },
        };

        try {
            if (!hookUrl) throw new Error(`通知机器人WebHook为空，无法进行通知`);
            if (!content) throw new Error(`通知内容不能为空`);

            postData['text']['content'] = content.substring(0, contentSize);

            await axios.post(hookUrl, postData);
        } catch (e) {
            Logger.log(`企微通知失败：${e.message}`);
        }

        return 'ok';
    }
}
